// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;

namespace Tierless.Framework.Data
{
	/// <summary>
	/// Represents possible entity tenses.
	/// </summary>
	public enum EntityTense
	{
		/// <summary>
		/// The entity represents information as it was at some point in the past.
		/// </summary>
		Past,

		/// <summary>
		/// The entity represents information as it is currently.
		/// </summary>
		Present,

		/// <summary>
		/// The entity represents information as it will be in the future.
		/// </summary>
		Future
	}

	/// <summary>
	/// Represents an element in an entity timeline.
	/// </summary>
	[DataContract]
	public class EntityTimelineElement
	{
		/// <summary>
		/// Initializes a new instance of the EntityTimelineElement class.
		/// </summary>
		/// <param name="date">The date of the entity timeline element.</param>
		public EntityTimelineElement(DateTime date)
		{
			_date = date;
		}

		[DataMember]
		private DateTime _date;

		/// <summary>
		/// Gets the date of the entity timeline element.
		/// </summary>
		public DateTime Date
		{
			get { return _date; }
		}

		[DataMember]
		private EntityTense? _tense;

		/// <summary>
		/// Gets the tense of the entity timeline element.
		/// </summary>
		public EntityTense Tense
		{
			get
			{
				if (_tense == null)
					_tense = DetermineTense();
				return _tense.GetValueOrDefault(EntityTense.Present);
			}
		}

		private EntityTense DetermineTense()
		{
			return
				_date < DateTime.Today
					? EntityTense.Past
					: (
						_date == DateTime.Today
							? EntityTense.Present
							: EntityTense.Future
						);
		}

		public override string ToString()
		{
			return Date.ToString("d");
		}
	}

	/// <summary>
	/// Represents an observable collection of entity timeline elements.
	/// </summary>
	public class EntityTimeline : ObservableCollection<EntityTimelineElement>
	{
		/// <summary>
		/// Returns the index of the timeline element with the given date, if it exists, -1 otherwise.
		/// </summary>
		/// <param name="date">The date of the timeline element to be retrieved.</param>
		/// <returns>The index of the timeline element with the given date, if it exists, -1 otherwise.</returns>
		public int IndexOf(DateTime date)
		{
			for (var index = Count - 1; index >= 0; index--)
			{
				if (date > this[index].Date)
					return ~(index + 1);
				if (date == this[index].Date)
					return index;
			}
			return -1;
		}

		/// <summary>
		/// Returns true if the timeline contains an element with the given date, false otherwise.
		/// </summary>
		/// <param name="date">The date to be tested.</param>
		/// <returns>True if the timeline contains an element with the given date, false otherwise.</returns>
		public bool Contains(DateTime date)
		{
			return IndexOf(date) >= 0;
		}

		protected override void InsertItem(int index, EntityTimelineElement element)
		{
			var targetIndex = IndexOf(element.Date);
			if (targetIndex >= 0)
				throw new InvalidOperationException(String.Format("Timeline already contains an element with the date '{0}'.", element.Date));

			base.InsertItem(~targetIndex, element);
		}

		#if !SILVERLIGHT
		protected override void MoveItem(int oldIndex, int newIndex)
		{
			throw new InvalidOperationException("Elements in the timeline are maintained in sorted order, and cannot be moved.");
		}
		#endif

		protected override void SetItem(int index, EntityTimelineElement element)
		{
			throw new InvalidOperationException("Elements in the timeline are maintained in sorted order, and cannot be updated.");
		}

		/// <summary>
		/// Adds the given elements to the timeline.
		/// </summary>
		/// <param name="elements"></param>
		public void AddRange(IEnumerable<EntityTimelineElement> elements)
		{
			foreach (var element in elements)
				Add(element);
		}

		/// <summary>
		/// Finds the timeline element corresponding to the given effective date, if one exists, false otherwise.
		/// </summary>
		/// <param name="asOf"></param>
		/// <returns></returns>
		public EntityTimelineElement FindElementAsOf(DateTime asOf)
		{
			for (var index = Count - 1; index >= 0; index--)
				if (this[index].Date <= asOf)
					return this[index];
			return null;
		}
	}
}